#!/usr/bin/env python3


"""
REFERENCES
*Embedding matplotlib plot via tkinter*
https://pythonprogramming.net/how-to-embed-matplotlib-graph-tkinter-gui/

*General tkinter documention (8.5)*
https://tkdocs.com/index.html
"""

import tkinter as tk
from tkinter import ttk

import numpy as np
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure

class DCM_CCM_TK(tk.PanedWindow):
	def __init__(self, master=None):
		super().__init__(master)
		self.master = master
		self.pack()
		self.orient=tk.HORIZONTAL

		configFrame = ttk.Labelframe(self, text='Config', width=1200, height=900)
		configFrame.pack(fill='both', expand='yes')
		self.add(configFrame)

		displayFrame = ttk.Labelframe(self, text='Plot', width=1200, height=900)
		self.add(displayFrame)	
		self._createScales(configFrame)
		self._initializePlot(displayFrame)

	def _createScales(self, configFrame):

		dcBounds = [1, 99] 
		dcDefault = 50 
		self.dutyCycleScale = tk.Scale(configFrame, \
			label="Duty Cycle", \
			orient=tk.HORIZONTAL, \
			from_=dcBounds[0], \
			to=dcBounds[1], \
			command=self.updatePlot
		)
		self.dutyCycleScale.set(dcDefault)
		self.dutyCycleScale.pack()

		ovBounds = [1, 50]
		ovDefault = 25
		self.outputVoltageScale = tk.Scale(configFrame, \
			label='Output Voltage [V]', \
			orient=tk.HORIZONTAL, \
			from_=ovBounds[0], \
			to=ovBounds[1], \
			command=self.updatePlot
		)	
		self.outputVoltageScale.set(ovDefault)
		self.outputVoltageScale.pack()

		lrBounds = [1, 50]
		lrDefault = 25
		self.loadResScale = tk.Scale(configFrame, \
			label="Load Resistance [Ohm]", \
			orient=tk.HORIZONTAL, \
			from_=lrBounds[0], \
			to=lrBounds[1], \
			command=self.updatePlot
		)
		self.loadResScale.set(lrDefault)
		self.loadResScale.pack()

		indBounds = [1, 10000]
		indDefault = 2500
		self.inductanceScale = tk.Scale(configFrame, \
			label='Inductance', \
		 	orient=tk.HORIZONTAL, \
		 	from_=indBounds[0], \
			to=indBounds[1], \
			command=self.updatePlot
		)	
		self.inductanceScale.set(indDefault)
		self.inductanceScale.pack()

		freqBounds = [20, 100]
		freqDefault = 50
		self.freqScale = tk.Scale(configFrame, \
			label='Frequency [Hz]', \
			orient=tk.HORIZONTAL, \
			from_=freqBounds[0], \
			to=freqBounds[1], \
			command=self.updatePlot
		)	
		self.freqScale.set(freqDefault)
		self.freqScale.pack()

	def _initializePlot(self, displayFrame):

		self.fig = Figure()
		self.ax = self.fig.add_subplot(111)

		canvas = FigureCanvasTkAgg(self.fig, displayFrame)
		canvas.draw()
		canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)

		toolbar = NavigationToolbar2Tk (canvas, displayFrame)
		toolbar.update()
		canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

		self.dutyVector = np.linspace(0, 1, 1000)
		self.invDutyVector = 1 - self.dutyVector

		dutyCycle = self.dutyCycleScale.get() 
		Vout = self.outputVoltageScale.get()
		Rload = self.loadResScale.get() 
		L = self.inductanceScale.get()*1e-9 
		freq = self.freqScale.get()*1e3

		# Derived Values
		Iout = Vout / Rload
		Vin = Vout*(1 - dutyCycle/100.0);

		Iin = (Vout*Iout) / Vin;
		I_LB = (Vout*self.invDutyVector*self.dutyVector) / (L*freq)
		I_OB = (Vout*(self.invDutyVector**2)*self.dutyVector) / (2*L*freq)

		# I_LB
		self.line_LB, = self.ax.plot(100*self.dutyVector, I_LB, 'b')

		# I_OB
		self.line_OB, = self.ax.plot(100*self.dutyVector, I_OB, 'r')

		# I_IN
		self.line_IN, = self.ax.plot(dutyCycle, Iin, 'go')

		# I_OUT
		self.line_OUT, = self.ax.plot(dutyCycle, Iout, 'ko')

		self.ax.set_xlabel('Duty Cycle [%]')
		self.ax.set_ylabel('Current [A]')
		self.ax.set_title('DCM CCM Mode Boost Converter')

	def updatePlot(self, value):

		dutyCycle = self.dutyCycleScale.get() 
		Vout = self.outputVoltageScale.get()
		Rload = self.loadResScale.get() 
		L = self.inductanceScale.get()*1e-9 
		freq = self.freqScale.get()*1e3

		# Derived Values
		Iout = Vout / Rload
		Vin = Vout*(1 - dutyCycle/100.0);

		Iin = (Vout*Iout) / Vin;
		I_LB = (Vout*self.invDutyVector*self.dutyVector) / (L*freq)
		I_OB = (Vout*(self.invDutyVector**2)*self.dutyVector) / (2*L*freq)

		# "Delete" the background contents -> Repaints an empty background
		self.ax.draw_artist(self.ax.patch)

		# Set New Data
		# I_LB
		self.line_LB.set_data(100.0*self.dutyVector, I_LB)
		self.ax.draw_artist(self.line_LB)

		# I_OB
		self.line_OB.set_data(100.0*self.dutyVector, I_OB)
		self.ax.draw_artist(self.line_OB)

		# I_IN
		self.line_IN.set_data(dutyCycle, Iin)
		self.ax.draw_artist(self.line_IN)

		# I_OUT
		self.line_OUT.set_data(dutyCycle, Iout)
		self.ax.draw_artist(self.line_OUT)
		
		currentData = []
		currentData.extend(I_LB)
		currentData.extend(I_OB)
		currentData.append(Iout)
		currentData.append(Iin)
		maxCurrent = np.max(currentData)
		currentLimit = maxCurrent + 0.10*maxCurrent
		self.ax.set_ylim([0, currentLimit])

		# Update figure
		self.fig.canvas.draw()
		self.fig.canvas.flush_events()

if __name__ == '__main__':
	root = tk.Tk()
	DCM_CCM_Interface = DCM_CCM_TK(master=root)
	DCM_CCM_Interface.mainloop()
